/*
 * $RCSfile: ChangeContext.java,v $$
 * $Revision: 1.1 $
 * $Date: 2016-11-10 $
 *
 * Copyright (C) 2008 Skin, Inc. All rights reserved.
 *
 * This software is the proprietary information of Skin, Inc.
 * Use is subject to license terms.
 */
package com.skin.webcat.database;

import java.util.ArrayList;
import java.util.List;

import com.skin.webcat.database.mysql.MySql;
import com.skin.webcat.util.Sql;

/**
 * <p>Title: ChangeContext</p>
 * <p>Description: </p>
 * <p>Copyright: Copyright (c) 2006</p>
 * @author xuesong.net
 * @version 1.0
 */
public class ChangeContext {
    private ChangeTable changeTable;
    private List<ChangeColumn> columnChangeList;
    private List<ChangeIndex> indexChangeList;

    /**
     * @param changeTable
     * @param columnChangeList
     * @param indexChangeList
     */
    public ChangeContext(ChangeTable changeTable, List<ChangeColumn> columnChangeList, List<ChangeIndex> indexChangeList) {
        this.changeTable = changeTable;
        this.columnChangeList = columnChangeList;
        this.indexChangeList = indexChangeList;
        this.prepare();
    }

    /**
     * @return the columnChangeList
     */
    public List<ChangeColumn> getColumnChangeList() {
        return this.columnChangeList;
    }

    /**
     * @param columnChangeList the columnChangeList to set
     */
    public void setColumnChangeList(List<ChangeColumn> columnChangeList) {
        this.columnChangeList = columnChangeList;
    }

    /**
     * @return the indexChangeList
     */
    public List<ChangeIndex> getIndexChangeList() {
        return this.indexChangeList;
    }

    /**
     * @param indexChangeList the indexChangeList to set
     */
    public void setIndexChangeList(List<ChangeIndex> indexChangeList) {
        this.indexChangeList = indexChangeList;
    }

    /**
     * 该方法对原始数据进行修正
     * 根据数据库的约束条件或者逻辑关系对数据进行调整
     */
    public void prepare() {
        if(this.columnChangeList == null || this.columnChangeList.size() < 1) {
            return;
        }

        /**
         * 约束
         * 1. 自增的列必须是主键
         * 2. 只能有一个自增主键
         */
        boolean flag = false;

        for(ChangeColumn changeColumn : this.columnChangeList) {
            ChangeValue columnName = changeColumn.getColumnName();
            ChangeValue nullable = changeColumn.getNullable();
            ChangeValue columnDef = changeColumn.getColumnDef();
            ChangeValue autoIncrement = changeColumn.getAutoIncrement();
            ChangeValue primaryKey = changeColumn.getPrimaryKey();

            if(flag) {
                autoIncrement.setNewValue("false");
            }

            /**
             * 如果是自增, 则一定是主键
             */
            if(autoIncrement.getBoolean()) {
                flag = true;
                primaryKey.setNewValue("true");
                nullable.setNewValue("false");
            }

            if(autoIncrement.getBoolean(autoIncrement.getOldValue())) {
                primaryKey.setOldValue("true");
                nullable.setOldValue("false");
            }

            /**
             * 如果是新增
             */
            if(columnName.isEmpty(columnName.getOldValue())) {
                nullable.setOldValue("true");
                columnDef.setOldValue("");
                primaryKey.setOldValue("false");
                autoIncrement.setOldValue("false");
            }
        }
    }

    /**
     * @return String
     */
    public String getCreateSql() {
        int maxLength = 0;
        StringBuilder buffer = new StringBuilder();
        String tableName = this.changeTable.getTableName().getNewValue();
        List<ChangeColumn> changeList = new ArrayList<ChangeColumn>();
        List<ChangeColumn> primaryKeys = new ArrayList<ChangeColumn>();

        buffer.append("-- ");
        buffer.append(tableName);
        buffer.append(": ");
        buffer.append(Sql.escape(this.changeTable.getRemarks().getNewValue()));
        buffer.append("\r\n");
        buffer.append("create table ");
        buffer.append(tableName);
        buffer.append("(\r\n");

        /**
         * 去除删除的列
         */
        for(ChangeColumn changeColumn : this.columnChangeList) {
            ChangeValue columnName = changeColumn.getColumnName();

            /**
             * 名称为空表示删除
             */
            if(columnName.isEmpty()) {
                continue;
            }

            if(columnName.getNewValue().length() > maxLength) {
                maxLength = columnName.getNewValue().length();
            }
            changeList.add(changeColumn);
        }

        for(int i = 0, size = changeList.size(); i < size; i++) {
            ChangeColumn changeColumn = changeList.get(i);
            ChangeValue columnName = changeColumn.getColumnName();
            ChangeValue autoIncrement = changeColumn.getAutoIncrement();
            ChangeValue primaryKey = changeColumn.getPrimaryKey();

            if(autoIncrement.getBoolean()) {
                primaryKey.setNewValue("true");
            }

            if(primaryKey.getBoolean()) {
                primaryKeys.add(changeColumn);
            }

            String sql = changeColumn.getCreateSql(tableName);
            buffer.append("    ");
            buffer.append(changeColumn.padding(columnName.getNewValue(), maxLength, " "));
            buffer.append("    ");
            buffer.append(sql);

            if((i + 1) < size || primaryKeys.size() > 0 || this.indexChangeList.size() > 0) {
                buffer.append(",\r\n");
            }
            else {
                buffer.append("\r\n");
            }
        }

        if(primaryKeys.size() > 0) {
            buffer.append("    primary key (");

            for(int i = 0, size = primaryKeys.size(); i < size; i++) {
                ChangeColumn changeColumn = this.columnChangeList.get(i);
                ChangeValue columnName = changeColumn.getColumnName();
                buffer.append(columnName.getNewValue());

                if((i + 1) < size) {
                    buffer.append(", ");
                }
            }

            if(this.indexChangeList.size() > 0) {
                buffer.append("),\r\n");
            }
            else {
                buffer.append(")\r\n");
            }
        }

        if(this.indexChangeList.size() > 0) {
            for(int i = 0, size = this.indexChangeList.size(); i < size; i++) {
                ChangeIndex changeIndex = this.indexChangeList.get(i);
                ChangeValue indexName = changeIndex.getIndexName();
                ChangeValue columnName = changeIndex.getColumnName();
                String nonUnique = changeIndex.getNonUnique().getNewValue();
                ChangeValue indexType = changeIndex.getIndexType();
                buffer.append("    ");

                if(nonUnique.equals("0")) {
                    buffer.append("UNIQUE ");
                }
                else if(nonUnique.equals("1")) {
                }
                else if(nonUnique.equals("2")) {
                    buffer.append("FULLTEXT ");
                    indexType.setNewValue("");
                }
                else if(nonUnique.equals("3")) {
                    buffer.append("SPATIAL ");
                    indexType.setNewValue("");
                }

                buffer.append("KEY ");
                buffer.append(indexName.getString());
                buffer.append(" (");
                buffer.append(columnName.getString());
                buffer.append(")");

                if(indexType.notEmpty()) {
                    buffer.append(" using ");
                    buffer.append(indexType.getString());
                }

                if((i + 1) < size) {
                    buffer.append(",\r\n");
                }
                else {
                    buffer.append("\r\n");
                }
            }
        }

        if(changeList.size() < 1) {
            buffer.append("    -- !!! 请添加列 !!!\r\n");
            buffer.append("    -- !!! 请添加列 !!!\r\n");
            buffer.append("    -- !!! 请添加列 !!!\r\n");
        }

        if(this.changeTable.getRemarks().notEmpty()) {
            buffer.append(") comment '");
            buffer.append(Sql.escape(this.changeTable.getRemarks().getNewValue()));
            buffer.append("';\r\n");
        }
        else {
            buffer.append(");\r\n");
        }
        return buffer.toString();
    }

    /**
     * @return String
     */
    public String getAlterSql() {
        StringBuilder buffer = new StringBuilder();
        String tableName = this.changeTable.getTableName().getNewValue();
        List<ChangeColumn> primaryKeyList = this.getNewPrimaryKeyList(this.columnChangeList);
        buffer.append(this.changeTable.getAlterSql(tableName));

        /**
         * 该方法实际上会扫描5次changeList
         * 这两个boolean可以放在第一次扫描里面检查, 这样可以减少2次扫描
         * 但是没有这样显得清楚, 所以多扫描2次吧
         * 条件变量必须在任何apply执行之前初始化
         */
        boolean hasOldPrimaryKey = this.hasOldPrimaryKey(this.columnChangeList);
        boolean primaryKeyChange = this.hasPrimaryKeyChange(this.columnChangeList);

        /**
         * 第一次扫描
         * 修改所有发生过变化并且不是自增的列
         */
        for(ChangeColumn changeColumn : this.columnChangeList) {
            ChangeValue columnName = changeColumn.getColumnName();
            ChangeValue typeName = changeColumn.getTypeName();
            ChangeValue columnSize = changeColumn.getColumnSize();
            ChangeValue decimalDigits = changeColumn.getDecimalDigits();
            ChangeValue nullable = changeColumn.getNullable();
            ChangeValue columnDef = changeColumn.getColumnDef();
            ChangeValue autoIncrement = changeColumn.getAutoIncrement();
            ChangeValue remarks = changeColumn.getRemarks();

            if(!changeColumn.modified()) {
                continue;
            }

            /**
             * 如果仅仅是修改了自增
             */
            if(!changeColumn.modified(columnName, typeName, columnSize, decimalDigits, nullable, columnDef, remarks)) {
                continue;
            }

            /**
             * 如果是新增的列
             */
            if(columnName.isEmpty(columnName.getOldValue())) {
                ChangeColumn model = changeColumn.clone();
                model.getAutoIncrement().set("true", "false");
                model.getPrimaryKey().set("true", "false");
                buffer.append(this.getAlterSql(tableName, model));
                changeColumn.apply(columnName, typeName, columnSize, decimalDigits, nullable, columnDef, remarks);
                continue;
            }

            /**
             * 如果生成了alter语句则置该列所有属性为生效状态
             * 以避免后面同样的alter语句再次生成
             * 此处只处理非自增字段
             */
            if(!autoIncrement.getBoolean()) {
                buffer.append(this.getAlterSql(tableName, changeColumn));
                changeColumn.apply();
            }
        }

        boolean dropAutoIncrement = false;

        /**
         * 第二次扫描
         * 删除原来的自增
         */
        for(ChangeColumn changeColumn : this.columnChangeList) {
            ChangeValue autoIncrement = changeColumn.getAutoIncrement();

            /**
             * 如果原来是自增  并且 (主键发生过变化或者自增修改过)
             * 1. 当存在主键需要发生变化的时候必须先删除自增
             * 2. 如果当前列原来是自增, 现在改为非自增
             */
            if(autoIncrement.getBoolean(autoIncrement.getOldValue())) {
                if(primaryKeyChange || autoIncrement.modified()) {
                    dropAutoIncrement = true;
                    ChangeColumn model = changeColumn.clone();
                    model.getAutoIncrement().set("true", "false");
                    model.getPrimaryKey().set("true", "false");
                    buffer.append("-- drop auto_increment\r\n");
                    buffer.append(this.getAlterSql(tableName, model));
                }
            }
        }

        /**
         * 检查原来是否存在主键, 如果存在则删除
         * 此处的处理相当于如下语法:
         * alter table [tableName] drop primary key if exists;
         * MySql没有这样的语法
         */
        if(primaryKeyChange) {
            if(hasOldPrimaryKey) {
                /**
                 * 删除当前主键
                 */
                buffer.append("-- drop primary key\r\n");
                buffer.append("alter table ");
                buffer.append(tableName);
                buffer.append(" drop primary key;\r\n");
            }

            /**
             * 创建新的主键
             * alter table [tableName] add primay key (c1, c2, c3);
             */
            buffer.append(this.getPrimaryKeySql(tableName, primaryKeyList));
        }

        /**
         * 第三次扫描
         * 如果某个主键是自增, 那么修改为自增
         * 前面由于主键约束条件可能会删除自增, 所以在创建完主键以后重新修改为自增
         */
        for(ChangeColumn changeColumn : this.columnChangeList) {
            ChangeValue autoIncrement = changeColumn.getAutoIncrement();

            /**
             * 如果是新增的自增
             */
            if(autoIncrement.getBoolean() && (dropAutoIncrement || autoIncrement.modified())) {
                ChangeColumn model = changeColumn.clone();
                model.getAutoIncrement().set("false", "true");
                model.getPrimaryKey().set("false", "false");
                buffer.append("-- set auto_increment\r\n");
                buffer.append(this.getAlterSql(tableName, model));

                /**
                 * 表示已生效
                 */
                changeColumn.apply();
            }

            /**
             * 如果上面的条件为true, 那么上面执行之后一定不会再触发此处的处理
             * 因为apply之后表示该字段已经生效, 将不会存在modified的情况
             */
            if(changeColumn.modified()) {
                /**
                 * 无论何种情况都不再设置自增
                 * 仅仅变更其他属性
                 */
                changeColumn.getAutoIncrement().set("false", "false");
                changeColumn.getPrimaryKey().set("false", "false");
                buffer.append(this.getAlterSql(tableName, changeColumn));
                changeColumn.apply();
            }
        }

        String removeIndexSql = ChangeIndex.getRemoveSql(tableName, this.indexChangeList);
        String createIndexSql = ChangeIndex.getAlterSql(tableName, this.indexChangeList);

        if(removeIndexSql != null && removeIndexSql.length() > 0) {
            buffer.append(removeIndexSql);
        }

        if(createIndexSql != null && createIndexSql.length() > 0) {
            buffer.append(createIndexSql);
        }
        return buffer.toString();
    }

    /**
     * @param tableName
     * @param primaryKeyList
     * @return String
     */
    private String getPrimaryKeySql(String tableName, List<ChangeColumn> primaryKeyList) {
        if(primaryKeyList == null || primaryKeyList.size() < 1) {
            return "";
        }

        StringBuilder buffer = new StringBuilder();
        buffer.append("-- add primary key\r\n");
        buffer.append("alter table ");
        buffer.append(tableName);
        buffer.append(" add primary key (");

        for(ChangeColumn changeColumn : primaryKeyList) {
            ChangeValue columnName = changeColumn.getColumnName();
            buffer.append(columnName.getNewValue());
            buffer.append(", ");
        }
        buffer.delete(buffer.length() - 2, buffer.length());
        buffer.append(");\r\n");
        return buffer.toString();
    }

    /**
     * 该方法将严格的根据列的属性生成列定义语句
     * 不会检查任何的约束, 所以任何情况下都不要修改该方法
     * @param tableName
     * @param changeColumn
     * @return String
     */
    public String getAlterSql(String tableName, ChangeColumn changeColumn) {
        StringBuilder buffer = new StringBuilder();
        ChangeValue columnName = changeColumn.getColumnName();
        ChangeValue typeName = changeColumn.getTypeName();
        ChangeValue columnSize = changeColumn.getColumnSize();
        ChangeValue decimalDigits = changeColumn.getDecimalDigits();
        ChangeValue nullable = changeColumn.getNullable();
        ChangeValue columnDef = changeColumn.getColumnDef();
        ChangeValue autoIncrement = changeColumn.getAutoIncrement();
        ChangeValue primaryKey = changeColumn.getPrimaryKey();
        ChangeValue remarks = changeColumn.getRemarks();

        if(!changeColumn.modified()) {
            return "";
        }

        /**
         * 如果新列名未指定则认为删除
         */
        if(columnName.isEmpty()) {
            buffer.append("-- drop column\r\n");
            buffer.append("alter table ");
            buffer.append(tableName);
            buffer.append(" drop ");
            buffer.append(columnName.getOldValue());
            buffer.append(";\r\n");
            return buffer.toString();
        }

        /**
         * 如果旧列名为空则认为新增
         */
        if(columnName.isEmpty(columnName.getOldValue())) {
            buffer.append("-- add column\r\n");
            buffer.append("alter table ");
            buffer.append(tableName);
            buffer.append(" add column " + columnName.getNewValue());
        }
        else {
            if(columnName.modified()) {
                buffer.append("-- change column\r\n");
                buffer.append("alter table ");
                buffer.append(tableName);
                buffer.append(" change " + columnName.getOldValue());
                buffer.append(" ");
                buffer.append(columnName.getNewValue());
            }
            else {
                buffer.append("-- modify column\r\n");
                buffer.append("alter table ");
                buffer.append(tableName);
                buffer.append(" modify " + columnName.getOldValue());
            }
        }

        buffer.append(" ");
        buffer.append(MySql.getColumnDefine(typeName.getString(), columnSize.getInt(), decimalDigits.getInt()));

        if(!nullable.getBoolean() && !autoIncrement.getBoolean()) {
            buffer.append(" not null");
        }

        if(columnDef.notEmpty() && !autoIncrement.getBoolean()) {
            buffer.append(" default '" + Sql.escape(columnDef.getString()) + "'");
        }

        /**
         * 1. 当表无主键时, 此时应该设置 auto_increment primary key
         * 2. 当表无主键并且新增多个主键，并且当前列是自增时, 应该先设置主键，再修改为自增
         */
        if(autoIncrement.getBoolean()) {
            buffer.append(" auto_increment");

            /**
             * 当有且仅有自增键时必须存在主键定义
             * 是否产生主键关键字由外部调用决定
             * 此处只要primaryKey为true就产生主键关键字
             */
            if(primaryKey.getBoolean()) {
                buffer.append(" primary key");
            }
        }

        if(remarks.notEmpty()) {
            buffer.append(" comment '" + Sql.escape(remarks.getString()) + "'");
        }
        buffer.append(";\r\n");
        return buffer.toString();
    }

    /**
     * @param changeList
     * @return ChangeColumn
     */
    protected ChangeColumn getAutoIncrement(List<ChangeColumn> changeList) {
        for(ChangeColumn changeColumn : changeList) {
            if(changeColumn.getAutoIncrement().getBoolean()) {
                return changeColumn;
            }
        }
        return null;
    }

    /**
     * @param changeList
     * @return boolean
     */
    public List<ChangeColumn> getOldPrimaryKeyList(List<ChangeColumn> changeList) {
        List<ChangeColumn> primaryKeys = new ArrayList<ChangeColumn>();

        for(ChangeColumn changeColumn : changeList) {
            ChangeValue autoIncrement = changeColumn.getAutoIncrement();
            ChangeValue primaryKey = changeColumn.getPrimaryKey();

            if(autoIncrement.getBoolean(autoIncrement.getOldValue()) || primaryKey.getBoolean(primaryKey.getOldValue())) {
                primaryKeys.add(changeColumn);
            }
        }
        return primaryKeys;
    }

    /**
     * @param changeList
     * @return ChangeColumn
     */
    protected List<ChangeColumn> getNewPrimaryKeyList(List<ChangeColumn> changeList) {
        List<ChangeColumn> primaryKeys = new ArrayList<ChangeColumn>();

        for(ChangeColumn changeColumn : changeList) {
            ChangeValue columnName = changeColumn.getColumnName();
            ChangeValue autoIncrement = changeColumn.getAutoIncrement();
            ChangeValue primaryKey = changeColumn.getPrimaryKey();

            if(columnName.isEmpty()) {
                continue;
            }

            if(autoIncrement.getBoolean() || primaryKey.getBoolean()) {
                primaryKeys.add(changeColumn);
            }
        }
        return primaryKeys;
    }

    /**
     * @param changeList
     * @return boolean
     */
    public boolean hasPrimaryKeyChange(List<ChangeColumn> changeList) {
        if(changeList == null || changeList.size() < 1) {
            return false;
        }

        for(ChangeColumn changeColumn : changeList) {
            ChangeValue primaryKey = changeColumn.getPrimaryKey();

            if(primaryKey.modified()) {
                return true;
            }
        }
        return false;
    }

    /**
     * @param changeList
     * @return boolean
     */
    public boolean hasOldPrimaryKey(List<ChangeColumn> changeList) {
        if(changeList == null || changeList.size() < 1) {
            return false;
        }

        for(ChangeColumn changeColumn : changeList) {
            ChangeValue primaryKey = changeColumn.getPrimaryKey();

            if(primaryKey.getBoolean(primaryKey.getOldValue())) {
                return true;
            }
        }
        return false;
    }
}
